AWS WAF で AppSync デフォルトドメインへのアクセスを制限できるか試してみた
いわさです。
先日、AppSyncでカスタムドメインを設定しました。
この記事のさいごにも記載しましたが、デフォルトドメインも引き続き使えるようです。
API Gatewayの場合はデフォルトドメインを無効化できますが、セキュリティ上mTLSを構成した時などに無効化することが推奨されていることなどもお伝えしました。
もしAppSyncでデフォルトドメインを無効化したい場合はどうしたらいいのかを調べてみました。
標準で無効化する方法はなさそう
「無効化」という機能自体は用意されていなさそうです。
AppSyncで完全にカスタムできそうなセキュリティ周りの機能としては以下が利用可能です。
AWS_LAMBDA 認証
AWS Lambdaを使って、独自のAPI認証ロジックを実装することが出来ます。
Lambda関数は次の形式のイベントを処理することが出来るようです。
{ "authorizationToken": "ExampleAUTHtoken123123123", "requestContext": { "apiId": "aaaaaa123123123example123", "accountId": "111122223333", "requestId": "f4081827-1111-4444-5555-5cf4695f339f", "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", "operationName": "MyQuery", "variables": {} } }
一度実装して検証してみる価値はありそうですが、今回の要件を処理するための情報として不足していそうな気もします。
AWS WAFを使用
前回の記事のさいごにも登場しましたが、AppSyncではAWS WAF統合機能があります。
Web ACLであればかなり柔軟なルール設定が出来るので、アクセス元のドメインなどで簡易的な制御は出来そうですね。
今回はこちらを試してみたいと思います。
AWS WAF で試してみる
前回の記事から推察するに、カスタムドメインはCloudFrontディストリビューションを経由してアクセスされるので、デフォルトドメインの場合とアクセス元情報などが結構異なっているはず、と思っていたのですが、どうやらAppSyncのエンドポイントはデフォルトでCloudFrontを経由するようになっているみたいです。(デフォルト作成したAppSyncの名前解決先がCloudFrontエッジになっていました)
カスタムドメインとデフォルトドメインでのリクエストの違いを確認する
まずはWAFを適用し、リクエストを確認してみたいと思います。
AppSyncコンソールの設定メニューからWeb application firewall
を有効化します。
有効化後に、特にブロックルールは設定しないWeb ACLを関連付けします。
POST /graphql accept: */* cloudfront-forwarded-proto: https cloudfront-is-desktop-viewer: true cloudfront-is-mobile-viewer: false cloudfront-is-smarttv-viewer: false cloudfront-is-tablet-viewer: false cloudfront-viewer-country: JP content-length: 66 content-type: application/graphql host: graphhoge.tak1wa.com user-agent: curl/7.64.1 via: 2.0 823ea75be36f9495c1eb23cb55639cd2.cloudfront.net (CloudFront) x-amz-cf-id: 9yU1mx0gHhDD4q8ROnen_r7fIme3NtNFgsDz3eyDUe2ZEjAkjgwI2Q== x-amzn-trace-id: Root=1-629beb91-23c5a2313c23ee126383410d x-api-key: <hogehogeapikey> x-appsync-dnsname: 5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com x-forwarded-for: 111.222.333.444, 130.176.135.164 x-forwarded-port: 443 x-forwarded-proto: https
POST /graphql accept: */* cloudfront-forwarded-proto: https cloudfront-is-desktop-viewer: true cloudfront-is-mobile-viewer: false cloudfront-is-smarttv-viewer: false cloudfront-is-tablet-viewer: false cloudfront-viewer-country: JP content-length: 66 content-type: application/graphql host: 5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com user-agent: curl/7.64.1 via: 2.0 70e24e789a7f5c3f75693b4d637a2d22.cloudfront.net (CloudFront) x-amz-cf-id: wFv8V3yj4HUM5Ujlu2eZXbKuEzj0v72wNTUY1LTmnTf4t0e-IlgASw== x-amzn-trace-id: Root=1-629beb7f-2befd8b90d44221215030c91 x-api-key: <hogehogeapikey> x-forwarded-for: 111.222.333.444, 52.46.48.81 x-forwarded-port: 443 x-forwarded-proto: https
カスタムドメインでアクセスした時のみ、x-appsync-dnsname
ヘッダーが追加されています。
上記ヘッダーの技術情報を見つけることが出来ませんでしたが、CloudFrontのどこかの段階で追加しているのでしょう。
なお、AppSyncのアクセスログではx-appsync-customdns
というヘッダーも存在していたのですが、WAFで制御する時点では存在してないヘッダーでした。もう少しあとの経路で追加されているヘッダーのようですね。
HTTPヘッダーで制御する
今回はカスタムドメインのみ許可して、デフォルトドメインをブロックしたいので、x-appsync-dnsname
ヘッダーで判定をしてみます。
ここではx-appsync-dnsname
を使っていますが、host
でも良いと思います。
どちらにせよ、このレベルでの制御はクライアントが明示的にヘッダーを追加した場合は通過出来るので、「一応設定しておくか」くらいの気持ちが大事かなと思います。
さて、先程関連付けしたACLにブロックルールを追加します。
これだけです。JSON定義だと以下のようになります。
{ "Name": "header-graphhoge", "Priority": 0, "Action": { "Block": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "header-graphhoge" }, "Statement": { "NotStatement": { "Statement": { "ByteMatchStatement": { "FieldToMatch": { "SingleHeader": { "Name": "x-appsync-dnsname" } }, "PositionalConstraint": "EXACTLY", "SearchString": "5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com", "TextTransformations": [ { "Type": "NONE", "Priority": 0 } ] } } } } }
リクエスト送ってみる
では確認してみましょう。
$ curl -X POST "https://5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com/graphql" \ -H "Content-Type:application/graphql" \ -H "x-api-key:<hogehogeapikey>" \ -d '{"query": "query ListEvents { listEvents { items { id name } } }"}' { "errors" : [ { "errorType" : "WAFForbiddenException", "message" : "Forbidden" } ] }
デフォルトドメインでリクエストを送信したところ、ブロックが出来ました。
次にカスタムドメインでリクエストを送信してみます。
$ curl -X POST "https://graphhoge.tak1wa.com/graphql" \ -H "Content-Type:application/graphql" \ -H "x-api-key:<hogehogeapikey>" \ -d '{"query": "query ListEvents { listEvents { items { id name } } }"}' {"data":{"listEvents":{"items":[{"id":"8b69181d-47ca-45a1-837d-e9a0dc6386d4","name":"My First Event"},{"id":"b00b5674-35bf-4ba0-b233-724378ac85ed","name":"My First Event"}]}}}
こちらは通過しましたね。
ヘッダーを明示的に付与してデフォルトドメインでリクエストを送ってみる
最後に、デフォルトドメインへリクエストを送信するが、ヘッダーを明示的に指定した場合です。
WAFでブロックするヘッダーをx-appsync-dnsname
、host
でそれぞれ試しましたが、どちらも通過出来ました。
curl -X POST "https://5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com/graphql" \ -H "Content-Type:application/graphql" \ -H "x-api-key:<hogehogeapikey>" \ -H "x-appsync-dnsname: 5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com" \ -d '{"query": "query ListEvents { listEvents { items { id name } } }"}' {"data":{"listEvents":{"items":[{"id":"8b69181d-47ca-45a1-837d-e9a0dc6386d4","name":"My First Event"},{"id":"b00b5674-35bf-4ba0-b233-724378ac85ed","name":"My First Event"}]}}} $ curl -X POST "https://5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com/graphql" \ -H "Content-Type:application/graphql" \ -H "x-api-key:<hogehogeapikey>" \ -H "host: graphhoge.tak1wa.com" \ -d '{"query": "query ListEvents { listEvents { items { id name } } }"}' {"data":{"listEvents":{"items":[{"id":"8b69181d-47ca-45a1-837d-e9a0dc6386d4","name":"My First Event"},{"id":"b00b5674-35bf-4ba0-b233-724378ac85ed","name":"My First Event"}]}}}
さいごに
本日は前回AppSyncでカスタムドメインを構成したので、その際にデフォルトドメインを無効化出来るのか、どういう方法があるのかを検証して試してみました。
簡易的ですが AWS WAFでヘッダーを判定してのブロックは出来ました。
カスタムドメインもデフォルトドメインも名前解決以外の経路は同じようなので、もう一段何か追加してIPアドレスなどでブロックする方法は出来るかもしれないなぁと思って、そこでやめました。